﻿using System;
using System.IO;
using System.Threading;
using System.Xml;
using log4net;
using Mistral.Xml;

namespace Mistral.IO
{
	/// <summary>
	/// Архиватор сообщений.
	/// </summary>
	public class Archiver
	{
		#region Fields

		private static readonly ILog __log = LogManager.GetLogger( typeof ( Archiver ) );
		static readonly object __saveLock = new object();

		#endregion

		#region Properties

		/// <summary>
		/// Базовая директория архива
		/// </summary>
		public string ArchiveDirectory { get; set; }

		/// <summary>
		/// Расширение файлов в архиве
		/// </summary>
		public string Extension { get; set; }

		public event Func<object,string> OnGetSubdirectory;
		public event Func<object,string> OnGetFileName;
		public event Func<object,string> OnGetExtension;

		#endregion

		#region Methods

		/// <summary>
		/// Сохранение в архиве.
		/// </summary>
		/// <param name="document">XmlDocument</param>
		public void SaveToArchive( XmlDocument document )
		{
			SaveToArchive( document, document );
		}

		/// <summary>
		/// Сохранение в архиве.
		/// </summary>
		/// <param name="document">XmlDocument.</param>
		///<param name="context">Контекст сообщения.</param>
		public void SaveToArchive( XmlDocument document, object context )
		{
			try
			{
				SaveToArchive( document.GetPrettyString(), context );
			}
			catch ( Exception e )
			{
				__log.Error( e.ToString(), e );
			}
		}

		///<summary>
		/// Сохранение в архиве.
		///</summary>
		///<param name="message">Сообщение.</param>
		public void SaveToArchive( string message )
		{
			SaveToArchive( message, (object)message );			
		}

		///<summary>
		/// Сохранение сообщения в архиве.
		///</summary>
		///<param name="message">Сообщение.</param>
		///<param name="context">Контекст сообщения.</param>
		public void SaveToArchive( string message, object context )
		{
			try
			{
				lock ( __saveLock )
				{ 
					// Полное имя файла
					string archiveFileName = GetFullFileName( context );

					// Пишем в файл
					File.WriteAllText( archiveFileName, message );
				}

			}
			catch ( Exception e )
			{
				__log.Error( e.ToString(), e );
			}			
		}

		/// <summary>
		/// Сохранение сообщения в архив с нужным расширением.
		/// </summary>
		public void SaveToArchive( object obj, string ext )
		{
			try
			{
				string message1 = ToString( obj );
				lock ( __saveLock )
				{ 
					// Полное имя файла
					string archiveFileName = GetFullFileName( obj );
					archiveFileName = Path.ChangeExtension( archiveFileName, ext );

					// Пишем в файл
					File.WriteAllText( archiveFileName, message1 );
				}
			}
			catch ( Exception e )
			{
				__log.Error( e.ToString(), e );
			}	
		}

		///<summary>
		/// Сохранение двух сообщений под одним именем, но с разными расширениями.
		///</summary>
		public void SaveToArchive( object obj1, string ext1, object obj2, string ext2 )
		{
			try
			{
				string message1 = ToString( obj1 );
				string message2 = ToString( obj2 );

				lock ( __saveLock )
				{ 
					string archiveFileName = GetFullFileName( obj1 );

					string archiveFileName1 = Path.ChangeExtension( archiveFileName, ext1 );
					string archiveFileName2 = Path.ChangeExtension( archiveFileName, ext2 );

					File.WriteAllText( archiveFileName1, message1 );
					File.WriteAllText( archiveFileName2, message2 );	
				}
			}
			catch ( Exception e )
			{
				__log.Error( e.ToString(), e );
			}	
		}

		static string ToString( object obj )
		{
			if( obj is string )
				return (string) obj;
			if( obj is XmlDocument )
				return (obj as XmlDocument).GetPrettyString();
			if( obj == null )
				return "empty string";
			return obj.ToString();
		}

		#endregion

		#region Protected interface

		/// <summary>
		/// Получение поддиректории архивной директории.
		/// По умолчанию создается в виде: Year/Month/Day
		/// </summary>
		protected virtual string GetSubdirectory( object context )
		{
			if( OnGetSubdirectory != null )
			{
				return OnGetSubdirectory( context );
			}

			// Текущее время
			DateTime dateTime = DateTime.Now;

			// Year/Month/Day
			return String.Format("{0}/{1}/{2}",dateTime.Year, dateTime.Month, dateTime.Day );
		}

		/// <summary>
		/// Получение имени файла.
		/// По умолчанию создается в виде: Year_Month_Day_Hour_Minute_Second_Millisecond.Extension
		/// </summary>
		protected virtual string GetFileName( object context )
		{
			if( OnGetFileName != null )
			{
				return OnGetFileName( context );
			}

			// Текущее время
			DateTime dateTime = DateTime.Now;

			string fileName = String.Format("{0}_{1:D2}_{2:D2}_{3:D2}_{4:D2}_{5:D2}_{6:D3}",
			                                dateTime.Year, dateTime.Month, dateTime.Day,
			                                dateTime.Hour, dateTime.Minute, dateTime.Second,
			                                dateTime.Millisecond );
			return fileName;
		}

		protected virtual string GetExtension( object context )
		{
			if( OnGetExtension!= null )
			{
				return OnGetExtension( context );
			}
			return Extension;
		}

		/// <summary>
		/// Получение полного имени файла.
		/// Полный путь - это комбинация: ArchiveDirectory/Subdirectory/FileName
		/// </summary>
		protected virtual string GetFullFileName( object context )
		{
			string archiveDirectory = null;
			string fileName = null;
			try
			{
				string subdirectory = GetSubdirectory( context );
				archiveDirectory = Path.Combine( ArchiveDirectory, subdirectory );
				Directory.CreateDirectory( archiveDirectory );
				fileName = GetFileName( context );
				fileName = Path.ChangeExtension( fileName, GetExtension( context ) );
			}
			catch ( Exception e )
			{
				__log.Error( "Ошибка формирования имени файла", e );
			}

			if( archiveDirectory == null )
				archiveDirectory = ArchiveDirectory;
			if( fileName == null )
				fileName = Guid.NewGuid().ToString();

			string fullFileName = Path.Combine( archiveDirectory, fileName );

			if( File.Exists( fullFileName ) )
			{
				Thread.Sleep( 1 );//Гарантия того, что время будет другим
				fullFileName = GetFullFileName( context );
			}

			return fullFileName;
		}

		#endregion		
	}
}
